﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;
using System.Runtime.InteropServices;
using System.Security;

using Prism.Core.Audio.DirectSound.WaveFormats;

namespace Prism.Core.Audio.DirectSound
{
    public static class COMObjectCollecter
    {
        public static List<COMObjectSet> COMObjectSets = new List<COMObjectSet>();
    }

    public class COMObjectSet
    {
        public int samplesTotalSize;
        public int samplesFrameSize;
        public int nextSamplesWriteIndex;

        public byte[] samples;

        internal NativeDirectSoundCOM.IDirectSound directSound = null;

        internal NativeDirectSoundCOM.IDirectSoundBuffer primarySoundBuffer = null;
        internal NativeDirectSoundCOM.IDirectSoundBuffer secondaryBuffer = null;
        public EventWaitHandle frameEventWaitHandle1;
        public EventWaitHandle frameEventWaitHandle2;
        public EventWaitHandle endEventWaitHandle;

        public DirectSoundAudio Audio { get; set; }
    }

    public class DirectSoundAudio
    {
        public WaveFormat waveFormat;
        public IWaveProvider waveStream = null;
        public event EventHandler InnerStopped;
        public event EventHandler PlaybackStopped;

        private InnerState innerState;

        public int desiredLatency;

        // Used purely for locking
        private Object locker = new Object();

        /// <summary>
        /// Initializes a new instance of the <see cref="DirectSoundOut"/> class.
        /// </summary>
        public DirectSoundAudio()
            : this(40)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DirectSoundOut"/> class.
        /// (40ms seems to work under Vista).
        /// </summary>
        /// <param name="latency">The latency.</param>
        public DirectSoundAudio(int latency)
        {
            this.desiredLatency = latency;
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="DirectSoundOut"/> is reclaimed by garbage collection.
        /// </summary>
        ~DirectSoundAudio()
        {
            this.Dispose();
        }

        public bool IsLoop { get; set; }

        /// <summary>
        /// Begin playback
        /// </summary>
        public void Play()
        {
            if (innerState == InnerState.Playing)
            {
                var waveStream = this.waveStream as WaveStream;
                waveStream.Position = 0;

                return;
            }

            if (innerState == InnerState.Stopped)
            {
                var notifyAction = new Action(delegate()
                {
                    var pso = new COMObjectSet() { Audio = this };
                    COMObjectCollecter.COMObjectSets.Add(pso);
                    System.Diagnostics.Debug.Print("PlayStateCOMObjectSets: " + COMObjectCollecter.COMObjectSets.Count.ToString());
                    this.processSamples(pso);
                });
                notifyAction.BeginInvoke(new AsyncCallback(delegate(IAsyncResult ar)
                {
                    if (ar.IsCompleted)
                    {
                        notifyAction.EndInvoke(ar);
                        lock (this.locker)
                        {
                            this.endAction = true;
                        }
                    }
                }), null);
            }

            lock (locker)
            {
                innerState = InnerState.Playing;
            }
        }

        private bool endAction = false;

        /// <summary>
        /// Stop playback
        /// </summary>
        public void Stop()
        {
            lock (locker)
            {
                this.innerState = DirectSound.InnerState.Stopped;
            }
            while (true)
            {
                if (this.endAction)
                {
                    lock (this.locker)
                    {
                        this.endAction = false;
                    }
                    break;
                }
            }
        }

        /// <summary>
        /// Pause Playback
        /// </summary>
        public void Pause()
        {
            lock (locker)
            {
                innerState = InnerState.Paused;
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (this.innerState != DirectSound.InnerState.Stopped)
                this.Stop();

            var q = from i in COMObjectCollecter.COMObjectSets
                    where i.Audio == this
                    select i;
            if (q.Count() > 0)
            {
                var cosList = new List<COMObjectSet>();
                foreach (var comObjectSet in q)
                {
                    Marshal.FinalReleaseComObject(comObjectSet.primarySoundBuffer);
                    Marshal.FinalReleaseComObject(comObjectSet.secondaryBuffer);
                    Marshal.FinalReleaseComObject(comObjectSet.directSound);
                    cosList.Add(comObjectSet);
                }
                foreach (var i in cosList)
                {
                    COMObjectCollecter.COMObjectSets.Remove(i);
                }
                cosList.Clear();
            }
        }

        public void Load(IWaveProvider inputStream)
        {
            this.waveStream = inputStream;
            this.waveFormat = this.waveStream.WaveFormat;
        }

        private void InitialiseDirectSound(COMObjectSet cOMObjectSet)
        {
            // Open DirectSound
            lock (this.locker)
            {
                cOMObjectSet.directSound = null;
                NativeDirectSoundCOM.DirectSoundCreate(IntPtr.Zero, out cOMObjectSet.directSound, IntPtr.Zero);

                if (cOMObjectSet.directSound != null)
                {
                    // Set Cooperative Level to PRIORITY (priority level can call the SetFormat and Compact methods)
                    cOMObjectSet.directSound.SetCooperativeLevel(NativeDirectSoundCOM.GetDesktopWindow(), NativeDirectSoundCOM.DirectSoundCooperativeLevel.DSSCL_PRIORITY);

                    // -------------------------------------------------------------------------------------
                    // Create PrimaryBuffer
                    // -------------------------------------------------------------------------------------

                    // Fill BufferDescription for PrimaryBuffer
                    var bufferDesc = new NativeDirectSoundCOM.BufferDescription();
                    bufferDesc.dwSize = Marshal.SizeOf(bufferDesc);
                    bufferDesc.dwBufferBytes = 0;
                    bufferDesc.dwFlags = NativeDirectSoundCOM.DirectSoundBufferCaps.DSBCAPS_PRIMARYBUFFER;
                    bufferDesc.dwReserved = 0;
                    bufferDesc.lpwfxFormat = IntPtr.Zero;
                    bufferDesc.guidAlgo = Guid.Empty;

                    // Create PrimaryBuffer
                    cOMObjectSet.directSound.CreateSoundBuffer(bufferDesc, out cOMObjectSet.primarySoundBuffer, IntPtr.Zero);

                    // Play & Loop on the PrimarySound Buffer 
                    cOMObjectSet.primarySoundBuffer.Play(0, 0, NativeDirectSoundCOM.DirectSoundPlayFlags.DSBPLAY_LOOPING);

                    // -------------------------------------------------------------------------------------
                    // Create SecondaryBuffer
                    // -------------------------------------------------------------------------------------

                    // A frame of samples equals to Desired Latency
                    cOMObjectSet.samplesFrameSize = MsToBytes(this.desiredLatency);

                    // Fill BufferDescription for SecondaryBuffer
                    var bufferDesc2 = new NativeDirectSoundCOM.BufferDescription();
                    bufferDesc2.dwSize = Marshal.SizeOf(bufferDesc2);
                    bufferDesc2.dwBufferBytes = (uint)(cOMObjectSet.samplesFrameSize * 2);
                    bufferDesc2.dwFlags = NativeDirectSoundCOM.DirectSoundBufferCaps.DSBCAPS_GETCURRENTPOSITION2
                        | NativeDirectSoundCOM.DirectSoundBufferCaps.DSBCAPS_CTRLPOSITIONNOTIFY
                        | NativeDirectSoundCOM.DirectSoundBufferCaps.DSBCAPS_GLOBALFOCUS
                        | NativeDirectSoundCOM.DirectSoundBufferCaps.DSBCAPS_CTRLVOLUME
                        | NativeDirectSoundCOM.DirectSoundBufferCaps.DSBCAPS_STICKYFOCUS;
                    //  
                    bufferDesc2.dwReserved = 0;
                    GCHandle handleOnWaveFormat = GCHandle.Alloc(waveFormat, GCHandleType.Pinned); // Ptr to waveFormat
                    bufferDesc2.lpwfxFormat = handleOnWaveFormat.AddrOfPinnedObject(); // set Ptr to waveFormat
                    // bufferDesc2.lpwfxFormat = IntPtr.Zero;
                    bufferDesc2.guidAlgo = Guid.Empty;

                    // Create SecondaryBuffer
                    cOMObjectSet.directSound.CreateSoundBuffer(bufferDesc2, out cOMObjectSet.secondaryBuffer, IntPtr.Zero);
                    handleOnWaveFormat.Free();

                    cOMObjectSet.secondaryBuffer.SetVolume(this.volume);

                    // Get effective SecondaryBuffer size
                    var dsbCaps = new NativeDirectSoundCOM.BufferCaps();
                    dsbCaps.dwSize = Marshal.SizeOf(dsbCaps);
                    cOMObjectSet.secondaryBuffer.GetCaps(dsbCaps);

                    cOMObjectSet.nextSamplesWriteIndex = 0;
                    cOMObjectSet.samplesTotalSize = dsbCaps.dwBufferBytes;
                    cOMObjectSet.samples = new byte[cOMObjectSet.samplesTotalSize];
                    System.Diagnostics.Debug.Assert(cOMObjectSet.samplesTotalSize == (2 * cOMObjectSet.samplesFrameSize), "Invalid SamplesTotalSize vs SamplesFrameSize");

                    this.ResetNotify(cOMObjectSet);
                }
            }
        }

        private void ResetNotify(COMObjectSet cOMObjectSet)
        {
            // -------------------------------------------------------------------------------------
            // Create double buffering notification.
            // Use DirectSoundNotify at Position [0, 1/2] and Stop Position (0xFFFFFFFF)
            // -------------------------------------------------------------------------------------
            var notify = (NativeDirectSoundCOM.IDirectSoundNotify)cOMObjectSet.secondaryBuffer;

            cOMObjectSet.frameEventWaitHandle1 = new EventWaitHandle(false, EventResetMode.AutoReset);
            cOMObjectSet.frameEventWaitHandle2 = new EventWaitHandle(false, EventResetMode.AutoReset);
            cOMObjectSet.endEventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);

            var notifies = new NativeDirectSoundCOM.DirectSoundBufferPositionNotify[3];
            notifies[0] = new NativeDirectSoundCOM.DirectSoundBufferPositionNotify();
            notifies[0].dwOffset = (UIntPtr)0;
            notifies[0].hEventNotify = cOMObjectSet.frameEventWaitHandle1.SafeWaitHandle.DangerousGetHandle();

            notifies[1] = new NativeDirectSoundCOM.DirectSoundBufferPositionNotify();
            notifies[1].dwOffset = (UIntPtr)cOMObjectSet.samplesFrameSize;
            notifies[1].hEventNotify = cOMObjectSet.frameEventWaitHandle2.SafeWaitHandle.DangerousGetHandle();

            notifies[2] = new NativeDirectSoundCOM.DirectSoundBufferPositionNotify();
            notifies[2].dwOffset = (UIntPtr)0xFFFFFFFF;
            notifies[2].hEventNotify = cOMObjectSet.endEventWaitHandle.SafeWaitHandle.DangerousGetHandle();

            notify.SetNotificationPositions(3, notifies);
        }

        /// <summary>
        /// Current playback state
        /// </summary>
        /// <value></value>
        public InnerState InnerState
        {
            get { return innerState; }
        }

        #region Volume Control

        private int volume;

        private bool IsNeedSetVolume = false;

        public float Volume
        {
            get
            {
                if (this.volume < -4000)
                {
                    return 0f;
                }
                else
                {
                    return 1 + (this.volume) / 4000f;
                }
            }
            set
            {
                if (value == 0)
                {
                    // Mute
                    this.volume = -10000;
                }
                else
                {
                    this.volume = (int)((value - 1) * 4000f);
                }

                this.IsNeedSetVolume = true;
            }
        }

        #endregion

        /// <summary>
        /// Determines whether the SecondaryBuffer is lost.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is buffer lost]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsBufferLost(COMObjectSet cOMObjectSet)
        {
            return (cOMObjectSet.secondaryBuffer.GetStatus() & NativeDirectSoundCOM.DirectSoundBufferStatus.DSBSTATUS_BUFFERLOST) != 0 ? true : false;
        }

        /// <summary>
        /// Convert ms to bytes size according to WaveFormat
        /// </summary>
        /// <param name="ms">The ms</param>
        /// <returns>number of byttes</returns>
        private int MsToBytes(int ms)
        {
            int bytes = ms * (waveFormat.AverageBytesPerSecond / 1000);
            bytes -= bytes % waveFormat.BlockAlign;
            return bytes;
        }

        /// <summary>
        /// Processes the samples in a separate thread.
        /// </summary>
        private void processSamples(COMObjectSet cOMObjectSet)
        {
            // Incase the thread is killed
            InitialiseDirectSound(cOMObjectSet);
            var lResult = 1;

            if (this.innerState == InnerState.Stopped)
            {
                cOMObjectSet.secondaryBuffer.SetCurrentPosition(0);
                cOMObjectSet.nextSamplesWriteIndex = 0;
                lResult = this.Feed(cOMObjectSet.samplesTotalSize, cOMObjectSet);
            }

            // Incase the previous Feed method returns 0
            if (lResult > 0)
            {
                lock (locker)
                {
                    this.innerState = InnerState.Playing;
                }

                cOMObjectSet.secondaryBuffer.Play(0, 0, NativeDirectSoundCOM.DirectSoundPlayFlags.DSBPLAY_LOOPING);

                var waitHandles = new WaitHandle[] { cOMObjectSet.frameEventWaitHandle1, cOMObjectSet.frameEventWaitHandle2, cOMObjectSet.endEventWaitHandle };

                while (InnerState != InnerState.Stopped)
                {
                    // Wait for signals on frameEventWaitHandle1 (Position 0), frameEventWaitHandle2 (Position 1/2)
                    var indexHandle = WaitHandle.WaitAny(waitHandles, 3 * this.desiredLatency, false);

                    // TimeOut is ok
                    if (indexHandle != WaitHandle.WaitTimeout)
                    {
                        // Buffer is Stopped
                        if (indexHandle == 2)
                        {
                            StopBuffer(cOMObjectSet);
                        }
                        else
                        {
                            indexHandle = (indexHandle == 0) ? 1 : 0;
                            cOMObjectSet.nextSamplesWriteIndex = indexHandle * cOMObjectSet.samplesFrameSize;

                            // Only carry on playing if we can!
                            if (this.Feed(cOMObjectSet.samplesFrameSize, cOMObjectSet) == 0)
                            {
                                StopBuffer(cOMObjectSet);
                            }
                        }
                    }
                    else
                    {
                        // Timed out!
                        StopBuffer(cOMObjectSet);
                    }
                }
            }

            this.StopBuffer(cOMObjectSet);

            // Fire playback stopped event
            if (InnerStopped != null)
            {
                InnerStopped(this, EventArgs.Empty);
            }
        }


        /// <summary>
        /// Stop playback
        /// </summary>
        private void StopBuffer(COMObjectSet cOMObjectSet)
        {
            lock (this.locker)
            {
                this.innerState = DirectSound.InnerState.Stopped;

                if (cOMObjectSet.secondaryBuffer != null)
                {
                    cOMObjectSet.secondaryBuffer.Stop();
                }
                if (cOMObjectSet.primarySoundBuffer != null)
                {
                    cOMObjectSet.primarySoundBuffer.Stop();
                }
            }
        }

        protected virtual void OnPlaybackStopped(EventArgs e)
        {
            if (this.PlaybackStopped != null)
            {
                this.PlaybackStopped(this, e);
            }
        }


        /// <summary>
        /// Feeds the SecondaryBuffer with the WaveStream
        /// </summary>
        /// <param name="bytesToCopy">number of bytes to feed</param>
        private int Feed(int bytesToCopy, COMObjectSet cOMObjectSet)
        {
            int bytesRead = bytesToCopy;

            // Restore the buffer if lost
            if (IsBufferLost(cOMObjectSet))
            {
                cOMObjectSet.secondaryBuffer.Restore();
            }

            if (this.IsNeedSetVolume)
            {
                cOMObjectSet.secondaryBuffer.SetVolume(this.volume);
                this.IsNeedSetVolume = false;
            }

            // Clear the bufferSamples if in Paused
            if (innerState == InnerState.Paused)
            {
                Array.Clear(cOMObjectSet.samples, 0, cOMObjectSet.samples.Length);
            }
            else if (innerState == DirectSound.InnerState.Stopped)
            {
                return 0;
            }
            else
            {
                // Read data from stream (Should this be inserted between the lock / unlock?)
                bytesRead = waveStream.Read(cOMObjectSet.samples, 0, bytesToCopy);

                var stream = waveStream as WaveStream;
                if (stream.Position > stream.Length)
                {
                    if (this.IsLoop)
                    {
                        stream.Position = 0;
                    }
                    else
                    {
                        this.OnPlaybackStopped(EventArgs.Empty);
                    }
                }

                if (bytesRead == 0)
                {
                    Array.Clear(cOMObjectSet.samples, 0, cOMObjectSet.samples.Length);
                    return 0;
                }
            }

            // Lock a portion of the SecondaryBuffer (starting from 0 or 1/2 the buffer)
            IntPtr wavBuffer1;
            int nbSamples1;
            IntPtr wavBuffer2;
            int nbSamples2;
            cOMObjectSet.secondaryBuffer.Lock(cOMObjectSet.nextSamplesWriteIndex, (uint)bytesRead,  // (uint)bytesToCopy,
                                 out wavBuffer1, out nbSamples1,
                                 out wavBuffer2, out nbSamples2,
                                 NativeDirectSoundCOM.DirectSoundBufferLockFlag.None);

            // Copy back to the SecondaryBuffer
            if (wavBuffer1 != IntPtr.Zero)
            {
                Marshal.Copy(cOMObjectSet.samples, 0, wavBuffer1, nbSamples1);
                if (wavBuffer2 != IntPtr.Zero)
                {
                    Marshal.Copy(cOMObjectSet.samples, 0, wavBuffer1, nbSamples1);
                }
            }

            // Unlock the SecondaryBuffer
            cOMObjectSet.secondaryBuffer.Unlock(wavBuffer1, nbSamples1, wavBuffer2, nbSamples2);

            return bytesRead;
        }
    }

    public class NativeDirectSoundCOM
    {
        //----------------------------------------------------------------------------------------------
        // Minimal Native DirectSound COM interop interfaces
        //----------------------------------------------------------------------------------------------
        #region Native DirectSound COM Interface

        [StructLayout(LayoutKind.Sequential, Pack = 2)]
        internal class BufferDescription
        {
            public int dwSize;
            [MarshalAs(UnmanagedType.U4)]
            public DirectSoundBufferCaps dwFlags;
            public uint dwBufferBytes;
            public int dwReserved;
            public IntPtr lpwfxFormat;
            public Guid guidAlgo;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 2)]
        internal class BufferCaps
        {
            public int dwSize;
            public int dwFlags;
            public int dwBufferBytes;
            public int dwUnlockTransferRate;
            public int dwPlayCpuOverhead;
        }

        internal enum DirectSoundCooperativeLevel : uint
        {
            DSSCL_NORMAL = 0x00000001,
            DSSCL_PRIORITY = 0x00000002,
            DSSCL_EXCLUSIVE = 0x00000003,
            DSSCL_WRITEPRIMARY = 0x00000004
        }

        [FlagsAttribute]
        internal enum DirectSoundPlayFlags : uint
        {
            DSBPLAY_LOOPING = 0x00000001,
            DSBPLAY_LOCHARDWARE = 0x00000002,
            DSBPLAY_LOCSOFTWARE = 0x00000004,
            DSBPLAY_TERMINATEBY_TIME = 0x00000008,
            DSBPLAY_TERMINATEBY_DISTANCE = 0x000000010,
            DSBPLAY_TERMINATEBY_PRIORITY = 0x000000020
        }

        internal enum DirectSoundBufferLockFlag : uint
        {
            None = 0,
            FromWriteCursor = 0x00000001,
            EntireBuffer = 0x00000002
        }

        [FlagsAttribute]
        internal enum DirectSoundBufferStatus : uint
        {
            DSBSTATUS_PLAYING = 0x00000001,
            DSBSTATUS_BUFFERLOST = 0x00000002,
            DSBSTATUS_LOOPING = 0x00000004,
            DSBSTATUS_LOCHARDWARE = 0x00000008,
            DSBSTATUS_LOCSOFTWARE = 0x00000010,
            DSBSTATUS_TERMINATED = 0x00000020
        }

        [FlagsAttribute]
        internal enum DirectSoundBufferCaps : uint
        {
            DSBCAPS_PRIMARYBUFFER = 0x00000001,
            DSBCAPS_STATIC = 0x00000002,
            DSBCAPS_LOCHARDWARE = 0x00000004,
            DSBCAPS_LOCSOFTWARE = 0x00000008,
            DSBCAPS_CTRL3D = 0x00000010,
            DSBCAPS_CTRLFREQUENCY = 0x00000020,
            DSBCAPS_CTRLPAN = 0x00000040,
            DSBCAPS_CTRLVOLUME = 0x00000080,
            DSBCAPS_CTRLPOSITIONNOTIFY = 0x00000100,
            DSBCAPS_CTRLFX = 0x00000200,
            DSBCAPS_STICKYFOCUS = 0x00004000,
            DSBCAPS_GLOBALFOCUS = 0x00008000,
            DSBCAPS_GETCURRENTPOSITION2 = 0x00010000,
            DSBCAPS_MUTE3DATMAXDISTANCE = 0x00020000,
            DSBCAPS_LOCDEFER = 0x00040000
        }

        [StructLayout(LayoutKind.Sequential, Pack = 2)]
        internal struct DirectSoundBufferPositionNotify
        {
            public UIntPtr dwOffset;
            public IntPtr hEventNotify;
        }

        /// <summary>
        /// IDirectSound interface
        /// </summary>
        [ComImport,
         Guid("279AFA83-4981-11CE-A521-0020AF0BE560"),
         InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
         SuppressUnmanagedCodeSecurity]
        internal interface IDirectSound
        {
            //STDMETHOD(CreateSoundBuffer)    (THIS_ LPCDSBUFFERDESC pcDSBufferDesc, LPDIRECTSOUNDBUFFER *ppDSBuffer, LPUNKNOWN pUnkOuter) PURE;
            void CreateSoundBuffer([In] BufferDescription desc, [Out, MarshalAs(UnmanagedType.Interface)] out IDirectSoundBuffer dsDSoundBuffer, IntPtr pUnkOuter);
            //STDMETHOD(GetCaps)              (THIS_ LPDSCAPS pDSCaps) PURE;
            void GetCaps(IntPtr caps);
            //STDMETHOD(DuplicateSoundBuffer) (THIS_ LPDIRECTSOUNDBUFFER pDSBufferOriginal, LPDIRECTSOUNDBUFFER *ppDSBufferDuplicate) PURE;
            void DuplicateSoundBuffer([In, MarshalAs(UnmanagedType.Interface)] IDirectSoundBuffer bufferOriginal, [In, MarshalAs(UnmanagedType.Interface)] IDirectSoundBuffer bufferDuplicate);
            //STDMETHOD(SetCooperativeLevel)  (THIS_ HWND hwnd, DWORD dwLevel) PURE;
            void SetCooperativeLevel(IntPtr HWND, [In, MarshalAs(UnmanagedType.U4)] DirectSoundCooperativeLevel dwLevel);
            //STDMETHOD(Compact)              (THIS) PURE;
            void Compact();
            //STDMETHOD(GetSpeakerConfig)     (THIS_ LPDWORD pdwSpeakerConfig) PURE;
            void GetSpeakerConfig(IntPtr pdwSpeakerConfig);
            //STDMETHOD(SetSpeakerConfig)     (THIS_ DWORD dwSpeakerConfig) PURE;
            void SetSpeakerConfig(uint pdwSpeakerConfig);
            //STDMETHOD(Initialize)           (THIS_ LPCGUID pcGuidDevice) PURE;
            void Initialize([In, MarshalAs(UnmanagedType.LPStruct)] Guid guid);
        }

        /// <summary>
        /// IDirectSoundBuffer interface
        /// </summary>
        [ComImport,
         Guid("279AFA85-4981-11CE-A521-0020AF0BE560"),
         InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
        SuppressUnmanagedCodeSecurity]
        internal interface IDirectSoundBuffer
        {
            //    STDMETHOD(GetCaps)              (THIS_ LPDSBCAPS pDSBufferCaps) PURE;
            void GetCaps([MarshalAs(UnmanagedType.LPStruct)] BufferCaps pBufferCaps);
            //    STDMETHOD(GetCurrentPosition)   (THIS_ LPDWORD pdwCurrentPlayCursor, LPDWORD pdwCurrentWriteCursor) PURE;
            void GetCurrentPosition([Out] out uint currentPlayCursor, [Out] out uint currentWriteCursor);
            //    STDMETHOD(GetFormat)            (THIS_ LPWAVEFORMATEX pwfxFormat, DWORD dwSizeAllocated, LPDWORD pdwSizeWritten) PURE;
            void GetFormat();
            //    STDMETHOD(GetVolume)            (THIS_ LPLONG plVolume) PURE;
            [return: MarshalAs(UnmanagedType.I4)]
            int GetVolume();
            //    STDMETHOD(GetPan)               (THIS_ LPLONG plPan) PURE;
            void GetPan([Out] out uint pan);
            //    STDMETHOD(GetFrequency)         (THIS_ LPDWORD pdwFrequency) PURE;
            [return: MarshalAs(UnmanagedType.I4)]
            int GetFrequency();
            //    STDMETHOD(GetStatus)            (THIS_ LPDWORD pdwStatus) PURE;
            [return: MarshalAs(UnmanagedType.U4)]
            DirectSoundBufferStatus GetStatus();
            //    STDMETHOD(Initialize)           (THIS_ LPDIRECTSOUND pDirectSound, LPCDSBUFFERDESC pcDSBufferDesc) PURE;
            void Initialize([In, MarshalAs(UnmanagedType.Interface)] IDirectSound directSound, [In] BufferDescription desc);
            //    STDMETHOD(Lock)                 (THIS_ DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1,
            //                                           LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags) PURE;
            void Lock(int dwOffset, uint dwBytes, [Out] out IntPtr audioPtr1, [Out] out int audioBytes1, [Out] out IntPtr audioPtr2, [Out] out int audioBytes2, [MarshalAs(UnmanagedType.U4)] DirectSoundBufferLockFlag dwFlags);
            //    STDMETHOD(Play)                 (THIS_ DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags) PURE;
            void Play(uint dwReserved1, uint dwPriority, [In, MarshalAs(UnmanagedType.U4)] DirectSoundPlayFlags dwFlags);
            //    STDMETHOD(SetCurrentPosition)   (THIS_ DWORD dwNewPosition) PURE;
            void SetCurrentPosition(uint dwNewPosition);
            //    STDMETHOD(SetFormat)            (THIS_ LPCWAVEFORMATEX pcfxFormat) PURE;
            void SetFormat([In] WaveFormat pcfxFormat);
            //    STDMETHOD(SetVolume)            (THIS_ LONG lVolume) PURE;
            void SetVolume(int volume);
            //    STDMETHOD(SetPan)               (THIS_ LONG lPan) PURE;
            void SetPan(uint pan);
            //    STDMETHOD(SetFrequency)         (THIS_ DWORD dwFrequency) PURE;
            void SetFrequency(uint frequency);
            //    STDMETHOD(Stop)                 (THIS) PURE;
            void Stop();
            //    STDMETHOD(Unlock)               (THIS_ LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioBytes2) PURE;
            void Unlock(IntPtr pvAudioPtr1, int dwAudioBytes1, IntPtr pvAudioPtr2, int dwAudioBytes2);
            //    STDMETHOD(Restore)              (THIS) PURE;
            void Restore();
        }

        /// <summary>
        /// IDirectSoundNotify interface
        /// </summary>
        [ComImport,
         Guid("b0210783-89cd-11d0-af08-00a0c925cd16"),
         InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
         SuppressUnmanagedCodeSecurity]
        internal interface IDirectSoundNotify
        {
            void SetNotificationPositions(uint dwPositionNotifies, [In, MarshalAs(UnmanagedType.LPArray)] DirectSoundBufferPositionNotify[] pcPositionNotifies);
        }

        /// <summary>
        /// Instanciate DirectSound from the DLL
        /// </summary>
        /// <param name="GUID">The GUID.</param>
        /// <param name="directSound">The direct sound.</param>
        /// <param name="pUnkOuter">The p unk outer.</param>
        [DllImport("dsound.dll", EntryPoint = "DirectSoundCreate", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        internal static extern void DirectSoundCreate(IntPtr GUID, [Out, MarshalAs(UnmanagedType.Interface)] out IDirectSound directSound, IntPtr pUnkOuter);

        /// <summary>
        /// Gets the HANDLE of the desktop window.
        /// </summary>
        /// <returns>HANDLE of the Desktop window</returns>
        [DllImport("user32.dll")]
        internal static extern IntPtr GetDesktopWindow();
        #endregion
    }
}
